package com.hk.core.autoconfigure.web.servlet;

import com.hk.commons.cache.ExpireCache;
import com.hk.commons.util.CollectionUtils;
import com.hk.commons.util.StringEscapeHandler;
import com.hk.commons.util.StringUtils;
import com.hk.core.autoconfigure.condition.ClientTokenCondition;
import com.hk.core.autoconfigure.exception.CustomAsyncUncaughtExceptionHandler;
import com.hk.core.autoconfigure.exception.UncaughtExceptionConsumer;
import com.hk.core.autoconfigure.web.ApplicationInfo;
import com.hk.core.autoconfigure.web.ApplicationInfoCustomizer;
import com.hk.core.autoconfigure.web.WebContextConfiguration;
import com.hk.core.web.ServletContextHolder;
import com.hk.core.web.filter.XssFilter;
import com.hk.core.web.interceptors.AsyncRequestHandlerInterceptor;
import com.hk.core.web.interceptors.GlobalPropertyInterceptor;
import com.hk.core.web.mvc.CustomRequestMappingHandlerMapping;
import com.hk.core.web.mvc.QueryModelRequestBodyAdvice;
import jakarta.servlet.Filter;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcRegistrations;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.format.FormatterRegistry;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

/**
 * Web mvc configurer
 *
 * @author Kevin
 * @date 2018-05-31 16:26
 */
@AutoConfiguration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
public class GeneralWebMvcConfigurer implements WebMvcConfigurer, AsyncConfigurer {

    private final ApplicationContext applicationContext;

    private final ApplicationInfo applicationInfo;

    public GeneralWebMvcConfigurer(ApplicationContext applicationContext,
                                   ObjectProvider<List<ApplicationInfoCustomizer>> applicationInfoCustomizers) {
        this.applicationContext = applicationContext;
        ApplicationInfo applicationInfo = new ApplicationInfo();
        var applicationInfoCustomizerList = applicationInfoCustomizers.getIfAvailable();
        if (Objects.nonNull(applicationInfoCustomizerList)) {
//            OrderComparator.sort(applicationInfoCustomizerList); //不需要再排序了，Spring已默认排好序
            applicationInfoCustomizerList.forEach(item -> item.customizer(applicationInfo));
        }
        this.applicationInfo = applicationInfo;
    }


    @Bean
    public ServletContextHolder servletContextHolder() {
        return new ServletContextHolder();
    }

//    /**
//     * 定义Bean 是一种方法，还需要在对应的类上加 {@link @ControllerAdvice} 注解
//     *
//     * 也可以在 {@link getRequestMappingHandlerAdapter} 方法 setRequestBodyAdvice 设置
//     */
//    @Bean
//    public QueryModelRequestBodyAdvice queryModelRequestBodyAdvice() {
//        return new QueryModelRequestBodyAdvice();
//    }

    @Bean(name = XssFilter.XSS_REQUEST_PREDICATE_BEAN_NAME)
    public StringEscapeHandler xssStringEscapeHandler() {
        return applicationInfo.getXssStringEscapeHandler();
    }

    /**
     * 使用配置方式
     */
    @Bean
    public FilterRegistrationBean<Filter> xssFilter() {
        var xssFilter = new XssFilter(applicationInfo.getServletInfo().getXssRequestPredicate(),
                applicationInfo.getXssStringEscapeHandler());
        var filterRegistrationBean = new FilterRegistrationBean<>();
        filterRegistrationBean.setAsyncSupported(true);
        // 这里是 /* ,不是 /**
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.setFilter(xssFilter);
        filterRegistrationBean.setName("xssFilter");
        filterRegistrationBean.setOrder(0);
        return filterRegistrationBean;
    }

    /**
     * 不需要在配置类上加 {@link org.springframework.web.servlet.config.annotation.EnableWebMvc} 注解，
     * 如果加了这个注解，这里设置的不会生效，而使用 {@link DelegatingWebMvcConfiguration} 的配置
     *
     * @see org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.EnableWebMvcConfiguration#mvcRegistrations
     */
    @Bean
    public WebMvcRegistrations webMvcRegistrations() {
        return new WebMvcRegistrations() {

            /**
             * 重写  RequestMappingHandlerMapping
             * @see WebMvcAutoConfiguration.EnableWebMvcConfiguration#createRequestMappingHandlerMapping()
             */
            @Override
            public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
                var handlerMapping = new CustomRequestMappingHandlerMapping();
                if (StringUtils.hasLength(applicationInfo.getRequestMappingParameterName())) {
                    handlerMapping.setRequestMappingParameterName(applicationInfo.getRequestMappingParameterName());
                }
                return handlerMapping;
            }

            @Override
            public RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
                var requestMappingHandlerAdapter = WebMvcRegistrations.super.getRequestMappingHandlerAdapter();
                if (Objects.isNull(requestMappingHandlerAdapter)) {
                    requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
                }
                requestMappingHandlerAdapter.setRequestBodyAdvice(Collections.singletonList(new QueryModelRequestBodyAdvice()));
                return requestMappingHandlerAdapter;
            }
        };
    }

//    /**
//     * 对 方法返回值的处理
//     */
//    @Override
//    public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
//        handlers.add(new RequestResponseBodyMethodProcessor());
//        handlers.add(new HttpEntityMethodProcessor(null));
//        handlers.add(new CallableMethodReturnValueHandler());
//        handlers.add(new DeferredResultMethodReturnValueHandler());
//    }


    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        WebMvcConfigurer.super.addArgumentResolvers(resolvers);
    }

    /**
     * 添加转换
     */
    @Override
    public void addFormatters(FormatterRegistry registry) {
        WebContextConfiguration.addFormatters(registry);
    }

//    /**
//     * 添加方法参数解析
//     *
//     * @param resolvers resolvers
//     */
//    @Override
//    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
//        resolvers.add(new LoginUserHandlerMethodArgumentResolver());
//    }


    @Override
    public Validator getValidator() {
        return WebMvcConfigurer.super.getValidator();
    }

    /**
     * 添加拦截器
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        applicationContext.getBeanProvider(ExpireCache.class)
                .ifAvailable(expireCache -> registry.addInterceptor(new RepeatSubmitHandlerInterceptor(applicationInfo.isRepeatSubmitPostOnly(), expireCache)));
        if (applicationInfo.isAddGlobalPropertyInterceptor()) {
            var property = applicationInfo.getProperty();
            if (CollectionUtils.isNotEmpty(property)) {
                var propertyInterceptor = new GlobalPropertyInterceptor();
                propertyInterceptor.setProperty(property);
                registry.addInterceptor(propertyInterceptor).addPathPatterns(applicationInfo.getGlobalPropertyInterceptorPatterns());
            }
        }
        registry.addInterceptor(new AsyncRequestHandlerInterceptor());

        if (ClientTokenCondition.hasClientToken(applicationContext.getEnvironment())) {
            registry.addInterceptor(new CleanSessionHandlerInterceptor()).addPathPatterns("/**");
        }

        /* ****************** 国际化支持******************* */
        if (applicationInfo.isAddLocaleChangeInterceptor()) {
            var localeChangeInterceptor = new LocaleChangeInterceptor();
            localeChangeInterceptor.setIgnoreInvalidLocale(applicationInfo.isLocalIgnoreInvalidLocale());
            localeChangeInterceptor.setParamName(applicationInfo.getLocalParamName());
            registry.addInterceptor(localeChangeInterceptor);
        }
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncUncaughtExceptionHandler(applicationContext.getBean(UncaughtExceptionConsumer.class));
    }
}

